package com.jsbs.iam.common.redis.service;

import com.jsbs.iam.common.core.utils.Json;
import com.jsbs.iam.common.core.utils.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class RedisUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger("redis");

    private static final String REDIS_KEY_CODE = "MobileCode:";

    private static StringRedisTemplate client = SpringUtils.getBean(StringRedisTemplate.class);

    public static String get(String key){
        LOGGER.debug("-->redis.get:{}", key);
        String ret = client.opsForValue().get(key);
        LOGGER.trace("<--:{}", ret);
        return ret;
    }

    public static void setCode(String key, String value, long time, TimeUnit unit) {
        client.opsForValue().set(codeKey(key), value);
        if (time > 0) {
            client.expire(codeKey(key), time, unit);
        }
    }

    public static String getCode(String key) {
        return client.opsForValue().get(codeKey(key));
    }

    public static String codeKey(String key) {
        return REDIS_KEY_CODE + key;
    }

    public List<String> mget(List<String> keys){
        return client.opsForValue().multiGet(keys);
    }

    public static void set(String key, String value){
        LOGGER.debug("-->redis.set:key={},value={}", key, value);
        client.opsForValue().set(key, value);
    }

    public static void set(String key, String value, long timeout, TimeUnit timeUnit){
        LOGGER.debug("-->redis.set:key={},value={},timeout={}", key, value, timeout);
        client.opsForValue().set(key, value, timeout, timeUnit);
    }

    public static <T> T getObject(String key, Class<T> clazz){
        LOGGER.debug("-->redis.getObject:{}", key);
        String json = client.opsForValue().get(key);
        LOGGER.trace("<--:{}", json);
        return null == json ? null : Json.parseJson(json, clazz);
    }

    public static <T> List<T> getObjectList(String key, Class<T> clazz){
        LOGGER.debug("-->redis.getObjectList:{}", key);
        String json = client.opsForValue().get(key);;
        LOGGER.trace("<--:{}", json);
        return null == json ? null : Json.parseJsonArray(json, clazz);
    }

    public static <T> void setObject(String key, T value){
        if(null == value){
            return;
        }
        String json = Json.toJsonString(value);
        LOGGER.debug("-->redis.setObject:key={},value={}", key, json);
        set(key, json);
    }

    public static <T> void setObjectList(String key, T value){
        if(null == value){
            return;
        }
        String json = Json.toJsonString(value);
        LOGGER.debug("-->redis.setObjectList:key={},value={}", key, json);
        set(key, json);
    }

    public static <T> void setObject(String key, T value, long timeout, TimeUnit timeUnit){
        if(null == value){
            return;
        }
        String json = Json.toJsonString(value);
        LOGGER.debug("-->redis.setObject:key={},value={},timeout={}", key, json, timeout);
        set(key, json, timeout, timeUnit);
    }

    public static <T> void setObjectList(String key, T value, long timeout, TimeUnit timeUnit){
        if(null == value){
            return;
        }
        String json = Json.toJsonString(value);
        LOGGER.debug("-->redis.setObjectList:key={},value={},timeout={}", key, json, timeout);
        set(key, json, timeout, timeUnit);
    }

    public void hset(String key, String field, Object value){
        LOGGER.debug("-->redis.hset:key={},field={},value={}", key, field, value);
        client.opsForHash().put(key, field, value);
    }

    public Object hget(String key, String field){
        LOGGER.debug("-->redis.hget:key={},field={}", key, field);
        Object ret =  client.opsForHash().get(key, field);
        LOGGER.trace("<--:{}", ret);
        return ret;
    }

    public List<Object> hmget(String key, List<Object> fields){
        LOGGER.debug("-->redis.hget:key={},field={}", key, Json.toJsonString(fields));
        List<Object> ret = client.opsForHash().multiGet(key, fields);
        LOGGER.trace("<--:{}", Json.toJsonString(ret));
        return ret;
    }

    public Map<Object, Object> hgetall(String key){
        LOGGER.debug("-->redis.hgetall:key={}", key);
        Map<Object, Object> entries = client.opsForHash().entries(key);
        LOGGER.trace("<--:{}", Json.toJsonString(entries));
        return entries;
    }

    /**
     * 获取自增id
     *
     * @param key
     * @return
     */
    public static Long  getAutoId(String key) {
        return client.opsForValue().increment(key);
    }


    /**
     * 如果之前已经存在 返回0，如果之前不存在返回1
     * @param key
     * @param value
     * @param score
     * @return
     */
    public static boolean zadd(String key, String value, double score){
        return client.opsForZSet().add(key, value, score);
    }

    public static long zcard(String key){
        return client.opsForZSet().zCard(key);
    }

    public static Set<String> zrange(String key, long start, long end){
        return client.opsForZSet().range(key, start, end);
    }

    public static long zremrangebyrank(String key, long start, long end){
        return client.opsForZSet().removeRange(key, start, end);
    }

    public static Set<String> keys(String pattern) {
        LOGGER.debug("-->redis.keys:pattern={}", pattern);
        return client.keys(pattern);
    }

    public static void del(String key){
        LOGGER.debug("-->redis.del:key={}", key);
        client.delete(key);
    }

    public static void del(Collection keys){
        LOGGER.debug("-->redis.del:keys={}", Json.toJsonString(keys));
        client.delete(keys);
    }

    public static boolean expire(String key, long timeout, TimeUnit timeUnit){
        LOGGER.debug("-->redis.expire:key={},timeout={}", key, timeout);
        return client.expire(key, timeout, timeUnit);
    }

    public static long incr(String key){
        return client.opsForValue().increment(key);
    }

    public static long incr(String key, long timeoutInSeconds){
        return executeLua("lua/incrWithExpire.lua", Long.class, Arrays.asList(key), Arrays.asList("1", String.valueOf(timeoutInSeconds)));
    }

    public static long incrBy(String key, long delta){
        return client.opsForValue().increment(key, delta);
    }

    public static long incrBy(String key, long delta, long timeoutInSeconds){
        return executeLua("lua/incrWithExpire.lua", Long.class, Arrays.asList(key), Arrays.asList(String.valueOf(delta), String.valueOf(timeoutInSeconds)));
    }

    public static long decr(String key){
        return client.opsForValue().decrement(key);
    }

    public static long decr(String key, long timeoutInSeconds){
        return executeLua("lua/incrWithExpire.lua", Long.class, Arrays.asList(key), Arrays.asList("-1", String.valueOf(timeoutInSeconds)));
    }

    public static long decrBy(String key, long delta){
        return client.opsForValue().decrement(key, delta);
    }

    public static long decrBy(String key, long delta, long timeoutInSeconds){
        return executeLua("lua/incrWithExpire.lua", Long.class, Arrays.asList(key), Arrays.asList(String.valueOf(-delta), String.valueOf(timeoutInSeconds)));
    }

    /**
     * redis加锁，成功返回true，失败返回false
     * @param scene 加锁场景，使用英文（最终作为redis key的一部分）
     * @param key 要锁的内容
     * @param timeout 锁的超时时间
     * @return
     */
    public static boolean lock(String scene, String key, long timeout){
        return client.opsForValue().setIfAbsent(getLockKey(scene, key), LocalDateTime.now().toString(), timeout, TimeUnit.MILLISECONDS);
    }

    /**
     * redis加锁，成功返回true，失败返回false
     * @param scene 加锁场景，使用英文（最终作为redis key的一部分）
     * @param key 要锁的内容
     * @param timeout 锁的超时时间
     * @param content 锁的内容
     * @return
     */
    public static boolean lock(String scene, String key, long timeout, String content){
        return client.opsForValue().setIfAbsent(getLockKey(scene, key), content, timeout, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取锁的redis的value内容
     * @param scene 加锁场景，使用英文（最终作为redis key的一部分）
     * @param key 要锁的内容
     * @return
     */
    public static String getLockCont(String scene, String key){
        return client.opsForValue().get(getLockKey(scene, key));
    }

    /**
     * 含有重试次数的redis加锁，成功返回true，失败返回false
     * @param scene 加锁场景，使用英文（最终作为redis key的一部分）
     * @param key 要锁的内容
     * @param timeout 锁的超时时间
     * @param retryIntervalTime 每次重试的间隔时间
     * @param retryTimes 总共的重试次数
     * @return
     */
    public static boolean lockWithRetry(String scene, String key, long timeout, long retryIntervalTime, int retryTimes){
        boolean suc = lock(scene, key, timeout);
        if(suc){
            return true;
        }else if(retryTimes > 0){
            for(int i = 0; i < retryTimes; i++){
                try {
                    TimeUnit.MILLISECONDS.sleep(retryIntervalTime);
                } catch (InterruptedException e) {
                    //不做处理
                }
                if(lock(scene, key, timeout)){
                    return true;
                }
            }
            return false;
        }
        return false;
    }

    /**
     * redis解锁，成功返回true，失败返回false
     * @param scene 解锁场景，使用英文，与加锁场景保持一致（最终作为redis key的一部分）
     * @param key 要解锁的内容，与加锁场景保持一致
     * @return
     */
    public static boolean unlock(String scene, String key){
        return client.delete(getLockKey(scene, key));
    }

    private static String getLockKey(String scene, String key) {
        return "LOCK:" + scene + ":" + key;
    }

    /**
     * 执行lua脚本
     * @param fileClasspath
     * @param returnType
     * @param keys
     * @param values
     * @param <T>
     * @return
     */
    public static <T> T executeLua(String fileClasspath, Class<T> returnType, List<String> keys, List<String> values){
        DefaultRedisScript<T> redisScript =new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(fileClasspath)));
        redisScript.setResultType(returnType);
        if(null == values){
            return client.execute(redisScript,keys,new Object[0]);
        }
        return client.execute(redisScript,keys,values.toArray(new Object[values.size()]));
    }

    /**
     * 执行lua脚本
     * @param fileClasspath
     * @param returnType
     * @param keys
     * @param <T>
     * @return
     */
    public static <T> T executeLua(String fileClasspath, Class<T> returnType, List<String> keys){
        return executeLua(fileClasspath, returnType, keys, null);
    }

    /**
     * 发布消息
     * @param channel
     * @param message
     */
    public static void pub(String channel, String message){
        LOGGER.debug("-->redis.pub:channel={},message={}", channel, message);
        client.convertAndSend(channel, message);
    }
}
